import FillStylePattern from './FillStylePattern'
import FillStyleLinearGradient from './FillStyleLinearGradient'
import FillStyleRadialGradient from './FillStyleRadialGradient'
import GImage from '../env/image.js'
import { ArrayBufferToBase64, Base64ToUint8ClampedArray } from '../env/tool.js'

export default class CanvasRenderingContext2D {
    _drawCommands = ''

    _globalAlpha = 1.0

    _fillStyle = 'rgb(0,0,0)'
    _strokeStyle = 'rgb(0,0,0)'

    _lineWidth = 1
    _lineCap = 'butt'
    _lineJoin = 'miter'

    _miterLimit = 10

    _globalCompositeOperation = 'source-over'

    _textAlign = 'start'
    _textBaseline = 'alphabetic'

    _font = '10px sans-serif'

    _savedGlobalAlpha = []

    timer = null
    componentId = null

    _notCommitDrawImageCache = []
    _needRedrawImageCache = []
    _redrawCommands = ''
    _autoSaveContext = true
    // _imageMap = new GHashMap();
    // _textureMap = new GHashMap();

    constructor() {
        this.className = 'CanvasRenderingContext2D'
        //this.save()
    }

    setFillStyle(value) {
        this.fillStyle = value
    }

    set fillStyle(value) {
        this._fillStyle = value

        if (typeof value == 'string') {
            this._drawCommands = this._drawCommands.concat('F' + value + ';')
        } else if (value instanceof FillStylePattern) {
            const image = value._img
            if (!image.complete) {
                image.onload = () => {
                    var index = this._needRedrawImageCache.indexOf(image)
                    if (index > -1) {
                        this._needRedrawImageCache.splice(index, 1)
                        CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
                        this._redrawflush(true)
                    }
                }
                this._notCommitDrawImageCache.push(image)
            } else {
                CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
            }

            //CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
            this._drawCommands = this._drawCommands.concat('G' + image._id + ',' + value._style + ';')
        } else if (value instanceof FillStyleLinearGradient) {
            var command =
                'D' +
                value._start_pos._x.toFixed(2) +
                ',' +
                value._start_pos._y.toFixed(2) +
                ',' +
                value._end_pos._x.toFixed(2) +
                ',' +
                value._end_pos._y.toFixed(2) +
                ',' +
                value._stop_count
            for (var i = 0; i < value._stop_count; ++i) {
                command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color
            }
            this._drawCommands = this._drawCommands.concat(command + ';')
        } else if (value instanceof FillStyleRadialGradient) {
            var command =
                'H' +
                value._start_pos._x.toFixed(2) +
                ',' +
                value._start_pos._y.toFixed(2) +
                ',' +
                value._start_pos._r.toFixed(2) +
                ',' +
                value._end_pos._x.toFixed(2) +
                ',' +
                value._end_pos._y.toFixed(2) +
                ',' +
                value._end_pos._r.toFixed(2) +
                ',' +
                value._stop_count
            for (var i = 0; i < value._stop_count; ++i) {
                command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color
            }
            this._drawCommands = this._drawCommands.concat(command + ';')
        }
    }

    get fillStyle() {
        return this._fillStyle
    }

    get globalAlpha() {
        return this._globalAlpha
    }

    setGlobalAlpha(value) {
        this.globalAlpha = value
    }

    set globalAlpha(value) {
        this._globalAlpha = value
        this._drawCommands = this._drawCommands.concat('a' + value.toFixed(2) + ';')
    }

    get strokeStyle() {
        return this._strokeStyle
    }

    setStrokeStyle(value) {
        this.strokeStyle = value
    }

    set strokeStyle(value) {
        this._strokeStyle = value

        if (typeof value == 'string') {
            this._drawCommands = this._drawCommands.concat('S' + value + ';')
        } else if (value instanceof FillStylePattern) {
            CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
            this._drawCommands = this._drawCommands.concat('G' + image._id + ',' + value._style + ';')
        } else if (value instanceof FillStyleLinearGradient) {
            var command =
                'D' +
                value._start_pos._x.toFixed(2) +
                ',' +
                value._start_pos._y.toFixed(2) +
                ',' +
                value._end_pos._x.toFixed(2) +
                ',' +
                value._end_pos._y.toFixed(2) +
                ',' +
                value._stop_count

            for (var i = 0; i < value._stop_count; ++i) {
                command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color
            }
            this._drawCommands = this._drawCommands.concat(command + ';')
        } else if (value instanceof FillStyleRadialGradient) {
            var command =
                'H' +
                value._start_pos._x.toFixed(2) +
                ',' +
                value._start_pos._y.toFixed(2) +
                ',' +
                value._start_pos._r.toFixed(2) +
                ',' +
                value._end_pos._x.toFixed(2) +
                ',' +
                value._end_pos._y +
                ','.toFixed(2) +
                value._end_pos._r.toFixed(2) +
                ',' +
                value._stop_count

            for (var i = 0; i < value._stop_count; ++i) {
                command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color
            }
            this._drawCommands = this._drawCommands.concat(command + ';')
        }
    }

    get lineWidth() {
        return this._lineWidth
    }

    setLineWidth(value) {
        this.lineWidth = value
    }

    set lineWidth(value) {
        this._lineWidth = value
        this._drawCommands = this._drawCommands.concat('W' + value + ';')
    }

    get lineCap() {
        return this._lineCap
    }

    setLineCap(value) {
        this.lineCap = value
    }

    set lineCap(value) {
        this._lineCap = value
        this._drawCommands = this._drawCommands.concat('C' + value + ';')
    }

    get lineJoin() {
        return this._lineJoin
    }

    setLineJoin(value) {
        this.lineJoin = value
    }

    set lineJoin(value) {
        this._lineJoin = value
        this._drawCommands = this._drawCommands.concat('J' + value + ';')
    }

    get miterLimit() {
        return this._miterLimit
    }

    setMiterLimit(value) {
        this.miterLimit = value
    }

    set miterLimit(value) {
        this._miterLimit = value
        this._drawCommands = this._drawCommands.concat('M' + value + ';')
    }

    get globalCompositeOperation() {
        return this._globalCompositeOperation
    }

    set globalCompositeOperation(value) {
        this._globalCompositeOperation = value
        let mode = 0
        switch (value) {
            case 'source-over':
                mode = 0
                break
            case 'source-atop':
                mode = 5
                break
            case 'source-in':
                mode = 0
                break
            case 'source-out':
                mode = 2
                break
            case 'destination-over':
                mode = 4
                break
            case 'destination-atop':
                mode = 4
                break
            case 'destination-in':
                mode = 4
                break
            case 'destination-out':
                mode = 3
                break
            case 'lighter':
                mode = 1
                break
            case 'copy':
                mode = 2
                break
            case 'xor':
                mode = 6
                break
            default:
                mode = 0
        }

        this._drawCommands = this._drawCommands.concat('B' + mode + ';')
    }

    get textAlign() {
        return this._textAlign
    }

    setTextAlign(value) {
        this.textAlign = value
    }

    set textAlign(value) {
        this._textAlign = value
        let Align = 0
        switch (value) {
            case 'start':
                Align = 0
                break
            case 'end':
                Align = 1
                break
            case 'left':
                Align = 2
                break
            case 'center':
                Align = 3
                break
            case 'right':
                Align = 4
                break
            default:
                Align = 0
        }

        this._drawCommands = this._drawCommands.concat('A' + Align + ';')
    }

    get textBaseline() {
        return this._textBaseline
    }

    setTextBaseline(value) {
        this.textBaseline = value
    }

    set textBaseline(value) {
        this._textBaseline = value
        let baseline = 0
        switch (value) {
            case 'alphabetic':
                baseline = 0
                break
            case 'middle':
                baseline = 1
                break
            case 'top':
                baseline = 2
                break
            case 'hanging':
                baseline = 3
                break
            case 'bottom':
                baseline = 4
                break
            case 'ideographic':
                baseline = 5
                break
            default:
                baseline = 0
                break
        }

        this._drawCommands = this._drawCommands.concat('E' + baseline + ';')
    }

    get font() {
        return this._font
    }

    setFontSize(size) {
        var str = this._font
        var strs = str.trim().split(/\s+/)
        for (var i = 0; i < strs.length; i++) {
            var values = [
                'normal',
                'italic',
                'oblique',
                'normal',
                'small-caps',
                'normal',
                'bold',
                'bolder',
                'lighter',
                '100',
                '200',
                '300',
                '400',
                '500',
                '600',
                '700',
                '800',
                '900',
                'normal',
                'ultra-condensed',
                'extra-condensed',
                'condensed',
                'semi-condensed',
                'semi-expanded',
                'expanded',
                'extra-expanded',
                'ultra-expanded',
            ]

            if (-1 == values.indexOf(strs[i].trim())) {
                if (typeof size === 'string') {
                    strs[i] = size
                } else if (typeof size === 'number') {
                    strs[i] = String(size) + 'px'
                }
                break
            }
        }
        this.font = strs.join(' ')
    }

    set font(value) {
        this._font = value
        this._drawCommands = this._drawCommands.concat('j' + value + ';')
    }

    setTransform(a, b, c, d, tx, ty) {
        this._drawCommands = this._drawCommands.concat(
            't' +
                (a === 1 ? '1' : a.toFixed(2)) +
                ',' +
                (b === 0 ? '0' : b.toFixed(2)) +
                ',' +
                (c === 0 ? '0' : c.toFixed(2)) +
                ',' +
                (d === 1 ? '1' : d.toFixed(2)) +
                ',' +
                tx.toFixed(2) +
                ',' +
                ty.toFixed(2) +
                ';',
        )
    }

    transform(a, b, c, d, tx, ty) {
        this._drawCommands = this._drawCommands.concat(
            'f' +
                (a === 1 ? '1' : a.toFixed(2)) +
                ',' +
                (b === 0 ? '0' : b.toFixed(2)) +
                ',' +
                (c === 0 ? '0' : c.toFixed(2)) +
                ',' +
                (d === 1 ? '1' : d.toFixed(2)) +
                ',' +
                tx +
                ',' +
                ty +
                ';',
        )
    }

    resetTransform() {
        this._drawCommands = this._drawCommands.concat('m;')
    }

    scale(a, d) {
        this._drawCommands = this._drawCommands.concat('k' + a.toFixed(2) + ',' + d.toFixed(2) + ';')
    }

    rotate(angle) {
        this._drawCommands = this._drawCommands.concat('r' + angle.toFixed(6) + ';')
    }

    translate(tx, ty) {
        this._drawCommands = this._drawCommands.concat('l' + tx.toFixed(2) + ',' + ty.toFixed(2) + ';')
    }

    save() {
        this._savedGlobalAlpha.push(this._globalAlpha)
        this._drawCommands = this._drawCommands.concat('v;')
    }

    restore() {
        this._drawCommands = this._drawCommands.concat('e;')
        this._globalAlpha = this._savedGlobalAlpha.pop()
    }

    createPattern(img, pattern) {
        if (typeof img === 'string') {
            var imgObj = new GImage()
            imgObj.src = img
            img = imgObj
        }
        return new FillStylePattern(img, pattern)
    }

    createLinearGradient(x0, y0, x1, y1) {
        return new FillStyleLinearGradient(x0, y0, x1, y1)
    }

    createRadialGradient = function (x0, y0, r0, x1, y1, r1) {
        return new FillStyleRadialGradient(x0, y0, r0, x1, y1, r1)
    }

    createCircularGradient = function (x0, y0, r0) {
        return new FillStyleRadialGradient(x0, y0, 0, x0, y0, r0)
    }

    strokeRect(x, y, w, h) {
        this._drawCommands = this._drawCommands.concat('s' + x + ',' + y + ',' + w + ',' + h + ';')
    }

    clearRect(x, y, w, h) {
        this._drawCommands = this._drawCommands.concat('c' + x + ',' + y + ',' + w + ',' + h + ';')
    }

    clip() {
        this._drawCommands = this._drawCommands.concat('p;')
    }

    resetClip() {
        this._drawCommands = this._drawCommands.concat('q;')
    }

    closePath() {
        this._drawCommands = this._drawCommands.concat('o;')
    }

    moveTo(x, y) {
        this._drawCommands = this._drawCommands.concat('g' + x.toFixed(2) + ',' + y.toFixed(2) + ';')
    }

    lineTo(x, y) {
        this._drawCommands = this._drawCommands.concat('i' + x.toFixed(2) + ',' + y.toFixed(2) + ';')
    }

    quadraticCurveTo = function (cpx, cpy, x, y) {
        this._drawCommands = this._drawCommands.concat('u' + cpx + ',' + cpy + ',' + x + ',' + y + ';')
    }

    bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
        this._drawCommands = this._drawCommands.concat(
            'z' +
                cp1x.toFixed(2) +
                ',' +
                cp1y.toFixed(2) +
                ',' +
                cp2x.toFixed(2) +
                ',' +
                cp2y.toFixed(2) +
                ',' +
                x.toFixed(2) +
                ',' +
                y.toFixed(2) +
                ';',
        )
    }

    arcTo(x1, y1, x2, y2, radius) {
        this._drawCommands = this._drawCommands.concat('h' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + ',' + radius + ';')
    }

    beginPath() {
        this._drawCommands = this._drawCommands.concat('b;')
    }

    fillRect(x, y, w, h) {
        this._drawCommands = this._drawCommands.concat('n' + x + ',' + y + ',' + w + ',' + h + ';')
    }

    rect(x, y, w, h) {
        this._drawCommands = this._drawCommands.concat('w' + x + ',' + y + ',' + w + ',' + h + ';')
    }

    fill() {
        this._drawCommands = this._drawCommands.concat('L;')
    }

    stroke(path) {
        this._drawCommands = this._drawCommands.concat('x;')
    }

    arc(x, y, radius, startAngle, endAngle, anticlockwise) {
        let ianticlockwise = 0
        if (anticlockwise) {
            ianticlockwise = 1
        }

        this._drawCommands = this._drawCommands.concat(
            'y' + x.toFixed(2) + ',' + y.toFixed(2) + ',' + radius.toFixed(2) + ',' + startAngle + ',' + endAngle + ',' + ianticlockwise + ';',
        )
    }

    fillText(text, x, y) {
        let tmptext = text.replace(/!/g, '!!')
        tmptext = tmptext.replace(/,/g, '!,')
        tmptext = tmptext.replace(/;/g, '!;')
        this._drawCommands = this._drawCommands.concat('T' + tmptext + ',' + x + ',' + y + ',0.0;')
    }

    strokeText = function (text, x, y) {
        let tmptext = text.replace(/!/g, '!!')
        tmptext = tmptext.replace(/,/g, '!,')
        tmptext = tmptext.replace(/;/g, '!;')
        this._drawCommands = this._drawCommands.concat('U' + tmptext + ',' + x + ',' + y + ',0.0;')
    }

    measureText(text) {
        return CanvasRenderingContext2D.GBridge.measureText(text, this.font, this.componentId)
    }

    isPointInPath = function (x, y) {
        throw new Error('GCanvas not supported yet')
    }

    drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
        if (typeof image === 'string') {
            var imgObj = new GImage()
            imgObj.src = image
            image = imgObj
        }
        if (image instanceof GImage) {
            if (!image.complete) {
                imgObj.onload = () => {
                    var index = this._needRedrawImageCache.indexOf(image)
                    if (index > -1) {
                        this._needRedrawImageCache.splice(index, 1)
                        CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
                        this._redrawflush(true)
                    }
                }
                this._notCommitDrawImageCache.push(image)
            } else {
                CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
            }
            var srcArgs = [image, sx, sy, sw, sh, dx, dy, dw, dh]
            var args = []
            for (var arg in srcArgs) {
                if (typeof srcArgs[arg] != 'undefined') {
                    args.push(srcArgs[arg])
                }
            }
            this.__drawImage.apply(this, args)
            //this.__drawImage(image,sx, sy, sw, sh, dx, dy, dw, dh);
        }
    }

    __drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
        const numArgs = arguments.length

        function drawImageCommands() {
            if (numArgs === 3) {
                const x = parseFloat(sx) || 0.0
                const y = parseFloat(sy) || 0.0

                return 'd' + image._id + ',0,0,' + image.width + ',' + image.height + ',' + x + ',' + y + ',' + image.width + ',' + image.height + ';'
            } else if (numArgs === 5) {
                const x = parseFloat(sx) || 0.0
                const y = parseFloat(sy) || 0.0
                const width = parseInt(sw) || image.width
                const height = parseInt(sh) || image.height

                return 'd' + image._id + ',0,0,' + image.width + ',' + image.height + ',' + x + ',' + y + ',' + width + ',' + height + ';'
            } else if (numArgs === 9) {
                sx = parseFloat(sx) || 0.0
                sy = parseFloat(sy) || 0.0
                sw = parseInt(sw) || image.width
                sh = parseInt(sh) || image.height
                dx = parseFloat(dx) || 0.0
                dy = parseFloat(dy) || 0.0
                dw = parseInt(dw) || image.width
                dh = parseInt(dh) || image.height

                return 'd' + image._id + ',' + sx + ',' + sy + ',' + sw + ',' + sh + ',' + dx + ',' + dy + ',' + dw + ',' + dh + ';'
            }
        }
        this._drawCommands += drawImageCommands()
    }

    _flush(reserve, callback) {
        const commands = this._drawCommands
        this._drawCommands = ''
        CanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback)
        this._needRender = false
    }

    _redrawflush(reserve, callback) {
        const commands = this._redrawCommands
        CanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback)
        if (this._needRedrawImageCache.length == 0) {
            this._redrawCommands = ''
        }
    }

    draw(reserve, callback) {
        if (!reserve) {
            this._globalAlpha = this._savedGlobalAlpha.pop()
            this._savedGlobalAlpha.push(this._globalAlpha)
            this._redrawCommands = this._drawCommands
            this._needRedrawImageCache = this._notCommitDrawImageCache
            if (this._autoSaveContext) {
                this._drawCommands = 'v;' + this._drawCommands
                this._autoSaveContext = false
            } else {
                this._drawCommands = 'e;X;v;' + this._drawCommands
            }
        } else {
            this._needRedrawImageCache = this._needRedrawImageCache.concat(this._notCommitDrawImageCache)
            this._redrawCommands += this._drawCommands
            if (this._autoSaveContext) {
                this._drawCommands = 'v;' + this._drawCommands
                this._autoSaveContext = false
            }
        }
        this._notCommitDrawImageCache = []
        if (this._flush) {
            this._flush(reserve, callback)
        }
    }

    getImageData(x, y, w, h, callback) {
        CanvasRenderingContext2D.GBridge.getImageData(this.componentId, x, y, w, h, function (res) {
            res.data = Base64ToUint8ClampedArray(res.data)
            if (typeof callback == 'function') {
                callback(res)
            }
        })
    }

    putImageData(data, x, y, w, h, callback) {
        if (data instanceof Uint8ClampedArray) {
            data = ArrayBufferToBase64(data)
            CanvasRenderingContext2D.GBridge.putImageData(this.componentId, data, x, y, w, h, function (res) {
                if (typeof callback == 'function') {
                    callback(res)
                }
            })
        }
    }

    toTempFilePath(x, y, width, height, destWidth, destHeight, fileType, quality, callback) {
        CanvasRenderingContext2D.GBridge.toTempFilePath(
            this.componentId,
            x,
            y,
            width,
            height,
            destWidth,
            destHeight,
            fileType,
            quality,
            function (res) {
                if (typeof callback == 'function') {
                    callback(res)
                }
            },
        )
    }
}
